package com.iman.common.hibernate;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import com.iman.common.util.StringUtils;

public class ReloadableTemplateQLServiceImpl extends TemplateQLServiceImpl {
    /** Cache to hold already loaded xmls per filename */
    private final Map<String, XmlHolder> cachedXmls = new HashMap<String, XmlHolder>();

    private org.springframework.beans.factory.xml.DocumentLoader documentLoader = new org.springframework.beans.factory.xml.DefaultDocumentLoader();
    
    //延迟加载10seconds,代替了Freemarker的delay,原来的Freemarker 用的nocache，setDelay(0),这个值在开发的时候可以设置为0，等上线的时候，可以用父类，也可以设置为60000
    private long delay = 10000; 
    
    public boolean reloadable = true;
    
    private long lastChecked;
    
    public long getDelay() {
        return delay;
    }

    public void setDelay(long delay) {
        this.delay = delay;
    }

    public boolean isReloadable() {
        return reloadable;
    }

    public void setReloadable(boolean reloadable) {
        this.reloadable = reloadable;
    }

    /**
     * We load the appropriate XML files by reading the
     * fileNames defined as this service's property.
     * For each XML file's HQL, we put the statements
     * in the Map with identifier as key.
     */
    public void afterPropertiesSet(){
        refreshLoad2Cache(true);
        lastChecked = System.currentTimeMillis();
    };

    @Override
    protected void refreshCache(String queryKey) {
        if(!reloadable){
            return;
        }
    	// for delay,注意：refresh_dealy是全局变量
    	long now = System.currentTimeMillis();
    	if(now - lastChecked < delay){
    		return;
    	}
    	// end for delay
        refreshLoad2Cache(false);
        lastChecked = System.currentTimeMillis();
//        Collection<XmlHolder> xmlHolders = cachedXmls.values();
//        for (XmlHolder holder : xmlHolders) {
//            String qlString = holder.getQl(queryKey);
//            if (StringUtils.isNotEmpty(qlString)) {
//            	setStringTemplate(queryKey, qlString);
//            }
//        }
    };

    //load queryString to XMLHolder and Freemarker Template
    private void refreshLoad2Cache(boolean isForce) {
        for (int i = 0; i < fileNames.size(); i++) {
            String fileName = ((String) fileNames.get(i)).trim();
            if (resourceLoader instanceof ResourcePatternResolver) {
                try {
                    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(fileName);
                    for (Resource resource : resources) {
                        getXmls(resource,isForce);
                    }
                } catch (IOException ex) {
                    throw new RuntimeException("Could not resolve sql definition resource pattern [" + fileName + "]", ex);
                }
            } else {
                Resource resource = resourceLoader.getResource(fileName);
                getXmls(resource,isForce);
            }
        }
    };

    /**
     * Get a PropertiesHolder for the given filename, either from the
     * cache or freshly loaded.
     * @param filename the bundle filename (basename + Locale)
     * @return the current PropertiesHolder for the bundle
     */
    protected XmlHolder getXmls(Resource resource, boolean isForce) {
        synchronized (this.cachedXmls) {
            String filename = resource.getFilename();
            XmlHolder cachedXmls = this.cachedXmls.get(filename);
            if (cachedXmls != null && (cachedXmls.getRefreshTimestamp() < 0 || cachedXmls.getRefreshTimestamp() > System.currentTimeMillis())) {
                return cachedXmls;
            }
            return refreshXmls(resource, cachedXmls, isForce);
        }
    };

    /**
     * Refresh the PropertiesHolder for the given bundle filename.
     * The holder can be <code>null</code> if not cached before, or a timed-out cache entry
     * (potentially getting re-validated against the current last-modified timestamp).
     * @param filename the bundle filename (basename + Locale)
     * @param propHolder the current PropertiesHolder for the bundle
     */
    protected XmlHolder refreshXmls(Resource resource, XmlHolder xmlHolder, boolean isForce) {
        String filename = resource.getFilename();
        long refreshTimestamp = System.currentTimeMillis();
        if (resource.exists()) {
            long fileTimestamp = -1;
            try {
                fileTimestamp = resource.lastModified();
                if (!isForce && xmlHolder != null && xmlHolder.getFileTimestamp() == fileTimestamp) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified");
                    }
                    xmlHolder.setRefreshTimestamp(refreshTimestamp);
                    return xmlHolder;
                }
            } catch (IOException ex) {
                // Probably a class path resource: cache it forever.
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(resource + " could not be resolved in the file system - assuming that is hasn't changed", ex);
                }
                fileTimestamp = -1;
            }
            try {
                Map qlMap = loadQlMap(resource);
                xmlHolder = new XmlHolder(qlMap, fileTimestamp);
            } catch (Exception ex) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Could not parse properties file [" + resource.getFilename() + "]", ex);
                }
                // Empty holder representing "not valid".
                xmlHolder = new XmlHolder();
            }
        } else {
            // Resource does not exist.
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
            }
            // Empty holder representing "not found".
            xmlHolder = new XmlHolder();
        }

        xmlHolder.setRefreshTimestamp(refreshTimestamp);
        this.cachedXmls.put(filename, xmlHolder);
        return xmlHolder;
    };

    /**
     * Put HQL statements in HashMap by using as key
     * defined in reading the defined XML file.
     */
//    protected Map<String,String> buildHQLMap(DefaultConfigurationBuilder builder, Resource resource) throws Exception {
//        Map<String, String> qlMap = new HashMap<String, String>();
//        Configuration config = builder.build(resource.getInputStream());
//        Configuration[] querys = config.getChildren("query");
//        for (int i = 0; i < querys.length; i++) {
//            String queryName = querys[i].getAttribute("name", "");
//            if (queryName.equals("")) {
//                throw new ConfigurationException("DynamicHibernate Service : name is essential attribute in a <query>.");
//            }
//            qlMap.put(queryName, querys[i].getValue());
//        }
//        return qlMap;
//    };
    
    protected Map<String,String> buildHQLMap(Resource resource) throws Exception {
        Map<String, String> qlMap = new HashMap<String, String>();
        try {
            InputSource inputSource = new InputSource(resource.getInputStream());
            org.w3c.dom.Document doc = this.documentLoader.loadDocument(inputSource, null, null, org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_NONE, false);
            Element root = doc.getDocumentElement();
            List<Element> querys = DomUtils.getChildElements(root);
            for(Element query:querys){
                String queryName = query.getAttribute("name");
                if (StringUtils.isEmpty(queryName)) {
                    throw new Exception("DynamicHibernate Service : name is essential attribute in a <query>.");
                }
                if(qlMap.containsKey(queryName)){
                    throw new Exception("DynamicHibernate Service : duplicated query in a <query>.");
                }
                String queryText = DomUtils.getTextValue(query);
                //qlMap.put(queryName, queryText ); //这句话其实没用，XmlHolder的qlMap也是没用的。XmlHolder只起到一个记录时间的作用。
                setStringTemplate(queryName, queryText); //ghost added at 20111216 
            }
        } catch (Exception ioe) {
            throw ioe;
        }
        return qlMap;
    };

    /**
     * Load the properties from the given resource.
     */
    protected Map loadQlMap(Resource resource) {
        Map qlMap = new HashMap<String, String>();
        InputStream is = null;
        try {
            is = resource.getInputStream();
            //return buildHQLMap(builder, resource);
            return buildHQLMap(resource);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is) {
                    is.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return qlMap;
    };

    /** wenzhi.zhang added , copy from spring ReloadableResourceBundleMessageResource
     *  XmlHolder for caching.
     * Stores the last-modified timestamp of the source file for efficient
     * change detection, and the timestamp of the last refresh attempt
     * (updated every time the cache entry gets re-validated).
     */
    protected class XmlHolder {
        private Map<String, String> qlMap;                //查询的映射
        private long                fileTimestamp    = -1;
        private long                refreshTimestamp = -1;

        public String getQl(String key) {
            if (null != qlMap) {
                return qlMap.get(key);
            } else {
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.debug("error is occured in getQl.......");
                }
                return "";
            }
        }

        public XmlHolder(Map<String, String> qlMap, long fileTimestamp) {
            this.qlMap = qlMap;
            this.fileTimestamp = fileTimestamp;
        }

        public XmlHolder() {
        }

        public Map<String, String> getQlMap() {
            return qlMap;
        }

        public long getFileTimestamp() {
            return fileTimestamp;
        }

        public void setRefreshTimestamp(long refreshTimestamp) {
            this.refreshTimestamp = refreshTimestamp;
        }

        public long getRefreshTimestamp() {
            return refreshTimestamp;
        }
    }
}
